﻿#ifndef YUTOPP_BASIC_CLIENT_HPP
#define YUTOPP_BASIC_CLIENT_HPP

#include "sync_part.hpp"
#include "async_part.hpp"

#include "basic_api.hpp"

#include "../protocol.hpp"
#include "../url_base64.hpp"

#include <string>
#include <tuple>

#include <boost/assign.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/mpl/if.hpp>
#include <boost/type_traits.hpp>

namespace webapp
{
	namespace clients
	{
		namespace detail
		{
			template<class Protocol, class Derived, class Policy>
			struct switch_part
				: boost::mpl::if_<
					protocol::is_sync_connection<Protocol>,
					detail::sync_part<Derived, Policy>,
					detail::async_part<Derived, Policy>
				>
			{};

			template<class Protocol, class R>
			struct return_val
				: boost::mpl::if_<
					protocol::is_sync_connection<Protocol>,
					R,
					void
				>
			{};
		}

		//
		template<class Derived, class Protocol, class Policy>
		class basic_client
			: public detail::switch_part<Protocol, basic_client<Derived, Protocol, Policy>, Policy>::type
		{
			friend detail::switch_part<Protocol, basic_client<Derived, Protocol, Policy>, Policy>::type;

			typedef typename Policy::translator_type	translator;
			typedef typename Policy::group_type			group_type;

		public:
			typedef typename Policy::auth_type			auth_type;
			typedef typename auth_type::key_type		auth_key_type;

			typedef typename Policy::string_type		string_type;
			typedef std::string							binary_type;
			typedef std::map<binary_type,binary_type>	header_type;
			typedef typename detail::return_val<
				Protocol, binary_type>::type			return_type;

			explicit basic_client( const auth_key_type& obj )
				: auth_( obj )
			{}

			//
			auth_key_type get_auth_key() const
			{
				return auth_.get_key();
			}

			bool is_complete() const
			{
				return auth_.get_key().is_complete();
			}

		private:
			typedef
				std::tuple<binary_type, binary_type, binary_type>
			address_type;

			//
			template<typename T, typename Handler>
			return_type request_cushion(
				const binary_type& ext,
				typename T::data_type::template traits<string_type>::parameter_type parameter,	//copy
				Handler& handler )
			{
				typedef typename T::data_type data_type;
				typedef typename request::template meta_body_process<T>::type proccess;
				const data_type type;
				const address_type addr = get_address<T>( ext, parameter );

				proccess()( parameter );	//ビミョー
				
				return request_cushion<data_type, Handler>(
					addr,
					parameter,
					generate_body( parameter, type ),
					type,
					handler
					);
			}

			template<typename Type, typename Handler>
			return_type request_cushion(
				  const address_type& address
				, const typename Type::template traits<string_type>::parameter_type&
				, const binary_type& body
				, const Type& type
				, Handler& handler )
			{
				return send_request(
					std::get<0>( address ),
					std::get<1>( address ),
					auth_.make_header(
						std::get<0>( address ), std::get<2>( address )
						),
					body,
					type(),
					handler
					);
			}
			// application/x-www-form-urlencodedのみOAuth生成にパラメータを渡す
			template<typename Type, typename Handler>
			return_type request_cushion(
				const address_type& address,
				const typename request::content::url_encoded::template traits<string_type>::parameter_type& parameter,
				const binary_type& body,
				const request::content::url_encoded& type,
				Handler& handler )
			{
				namespace adp = boost::adaptors;
				namespace ued = request::content::_url_encoded::detail;

				return send_request(
					std::get<0>( address ),
					std::get<1>( address ),
					auth_.make_header(
						  std::get<0>( address ),
						  std::get<2>( address ),
						  parameter
							| adp::transformed(
								ued::encode<translator::encoder_type,
								typename request::content::url_encoded::template traits<string_type>::parameter_type::value_type>
								)
						),
					body,
					type(),
					handler
					);
			}

			//
			template<class T>
			address_type get_address(
				const binary_type& ext,
				const typename T::data_type::template traits<string_type>::parameter_type& parameter ) const
			{
				// to do - urlのエンコード処理

				typedef typename request::template meta_address_process<T>::type proccess;
				//const translator::encoder_type e;

				const binary_type method = request::request_method<T>::name();
				const binary_type path = binary_type( Policy::get_path() ) + proccess()( T::path(), parameter ) + ( ext.empty() ? "" : "." + ext );
				const binary_type url = binary_type( Protocol::name() ) + "://" + Policy::get_domain() + path;

				return address_type( method, path, url );
			}
			address_type get_address(
				  const binary_type& method
				, const binary_type/*string_type*/& path_address
				, const binary_type& ext ) const
			{
				// to do - urlのエンコード処理
				
				const binary_type path = Policy::get_path() + path_address + ( ext.empty() ? "" : "." + ext );
				const binary_type url = binary_type( Protocol::name() ) + "://" + Policy::get_domain() + path;

				return address_type( method, path, url );
			}

			//
			binary_type generate_body(
				const typename request::content::url_encoded::template traits<string_type>::parameter_type& parameter,
				const request::content::url_encoded& ) const
			{
				const typename request::content::url_encoded::template traits<
					string_type
				>::template generator<
					translator::encoder_type
				> gen;

				return gen( parameter );
			}
			binary_type generate_body(
				const typename request::content::form_data::template traits<string_type>::parameter_type& parameter,
				const request::content::form_data& type ) const
			{
				const typename request::content::form_data::template traits<
					string_type
				>::template generator<
					translator::encoder_type
				> gen;

				return gen( type.get_boundary(), parameter );
			}
			binary_type generate_body(
				const typename request::content::atom::template traits<string_type>::parameter_type& parameter,
				const request::content::atom& ) const
			{
				const typename request::content::atom::template traits<
					string_type
				>::template generator<
					translator::encoder_type
				> gen;

				return gen( parameter );
			}

			//
			header_type make_request(
				const binary_type& auth_header,
				const binary_type& body,
				const binary_type& content_type ) const
			{
				return boost::assign::map_list_of<binary_type, binary_type>
					( "Host", Policy::get_domain() )
					( "Accept-Charset", translator::encoder_type::name() )
					( "User-Agent", Policy::get_user_agent() )
					( "Content-type", content_type )
					( "Content-Length", boost::lexical_cast<binary_type>( body.size() ) )
					( "Connection", "close" ).range(
						static_cast<const Derived&>(*this).request_field(
							auth_, auth_header
							)
						);
			}

			template<typename Handler>
			return_type send_request(
				const binary_type& method,
				const binary_type& path,
				const binary_type& auth_header,
				const binary_type& body,
				const binary_type& content_type,
				Handler& handler )
			{
				return sender_(
					Policy::get_domain(),
					method,
					path,
					make_request(
						auth_header,
						body,
						content_type
						),
					body,
					handler
					);
			}


			// - 派生クラスで書き換え可能
			header_type request_field(
				const auth_type&,
				const binary_type& auth_header
				) const
			{
				return boost::assign::map_list_of
					( "Authorization", auth_header );
			}

			auth_type auth_;	// 認証
			Protocol sender_;	// 通信
		};
	} // - clients
} // - webapp

#endif /*YUTOPP_BASIC_CLIENT_HPP*/
